#!/usr/bin/python3
# -*- coding: utf-8 -*-

import os
import sys
import time
import collections
import zipfile
import tarfile
import rarfile
import py7zlib

try: import Tkinter as tk  # Python 2
except ImportError: import tkinter as tk  # Python 3
try: import ttk
except ImportError: import tkinter.ttk as ttk

sys.path.insert(0, os.path.expanduser('~') + '/lib/python')

# from util import hsize, get_encoding, get_diff_folder, \
#     get_folder_and_file_list, get_folder_and_file_names, get_filename_folder, get_intelligent_folder
import mylib.util as util


class Const:
    CF_FILENAME = 1
    CF_INTELLIGENT = 2
    CF_CURRENT = 3
    CF_NOFOLDER = 4

    CT_NONE = 0
    CT_ZIP = 1
    CT_TAR = 2
    CT_TAR_XZ = 3
    CT_TAR_GZ = 4
    CT_TAR_BZ2 = 5
    CT_RAR = 6
    CT_7Z = 7
    DEBUG = False


def get_file_type(filename):
    filename = filename.lower()
    if filename.endswith('.zip'): return Const.CT_ZIP, '.zip'
    elif filename.endswith('.tar'): return Const.CT_TAR, '.tar'
    elif filename.endswith('.txz'): return Const.CT_TAR_XZ, '.tar.xz'
    elif filename.endswith('.tar.xz'): return Const.CT_TAR_XZ, '.tar.xz'
    elif filename.endswith('.tgz'): return Const.CT_TAR_GZ, '.tar.gz'
    elif filename.endswith('.tar.gz'): return Const.CT_TAR_GZ, '.tar.gz'
    elif filename.endswith('.gz'): return Const.CT_TAR_GZ, '.tar.gz'
    elif filename.endswith('.tbz'): return Const.CT_TAR_BZ2, '.tar.bz2'
    elif filename.endswith('.tbz2'): return Const.CT_TAR_BZ2, '.tar.bz2'
    elif filename.endswith('.tar.bz2'): return Const.CT_TAR_BZ2, '.tar.bz2'
    elif filename.endswith('.bz2'): return Const.CT_TAR_BZ2, '.tar.bz2'
    elif filename.endswith('.rar'): return Const.CT_RAR, '.rar'
    elif filename.endswith('.7z'): return Const.CT_7Z, '.7z'
    else: return Const.CT_NONE, ''


FileInfo = collections.namedtuple('fileinfo', 'name size dtm')


class MyArc(object):
    def __init__(self):
        self.filename = ''
        self._is_open = False
        self._type = Const.CT_NONE
        self.files = []
        self.file_object = None
        self.fd = None
        self.ENCODING = 'auto'
        self.DECODING = 'utf-8'
        rarfile.PATH_SEP = os.path.sep

    def is_open(self):
        return self._is_open

    def open(self, name, mode='r', _type=Const.CT_ZIP):
        self._type, ext = get_file_type(name)
        if mode == 'w': self._type = _type
        # noinspection PyBroadException
        try:
            if self._type == Const.CT_ZIP:
                self.file_object = zipfile.ZipFile(name, mode=mode)
            elif self._type == Const.CT_TAR:
                self.file_object = tarfile.open(name, mode)
            elif self._type == Const.CT_TAR_XZ:
                self.file_object = tarfile.open(name, mode + ':xz')
            elif self._type == Const.CT_TAR_GZ:
                self.file_object = tarfile.open(name, mode + ':gz')
            elif self._type == Const.CT_TAR_BZ2:
                self.file_object = tarfile.open(name, mode + ':bz2')
            elif self._type == Const.CT_RAR:
                self.file_object = rarfile.RarFile(name)
            elif self._type == Const.CT_7Z:
                self.fd = open(name, mode + 'b')
                self.file_object = py7zlib.Archive7z(self.fd)
            else:
                print('What is it?? ' + name)
                return False
            print('Open ' + ext + ' File : ' + name)
            self.filename = name
            self._is_open = True
            return True
        except:
            print('File Open Error : ' + name)
            return False

    def check_tar(self):
        block_size = 1024

        try: members = self.file_object.getmembers()
        except:
            self.file_object.close()
            return "Read Error"

        for member_info in members:
            try:
                check = self.file_object.extractfile(member_info.name)
                while 1:
                    data = check.read(block_size)
                    if not data: break
            except:
                self.file_object.close()
                return member_info.name

        return None

    def check(self):
        ret = None
        if self._type == Const.CT_ZIP:
            ret = self.file_object.testzip()
        elif self._type == Const.CT_RAR:
            ret = self.file_object.testrar()
        elif self._type in (Const.CT_TAR, Const.CT_TAR_GZ, Const.CT_TAR_BZ2):
            ret = self.file_object.check_tar()

        return ret

    def compress(self, file_or_dir):
        dl, fl = util.get_folder_and_file_list(file_or_dir, True)
        dfl = dl + fl
        # dfl.extend(dl)
        # dfl.extend(fl)
        for ff in dfl:
            print(ff)
            if self._type == Const.CT_ZIP:
                self.file_object.write(ff)
            elif self._type in (Const.CT_TAR, Const.CT_TAR_GZ, Const.CT_TAR_BZ2):
                self.file_object.add(ff)

    def extract_all(self, dest, encoding='auto', cf=Const.CF_CURRENT, selection=None):
        if not self._is_open: return None
        if self._type == Const.CT_NONE: return None
        if self._type in (Const.CT_ZIP, Const.CT_RAR):
            names = self.file_object.namelist()
            files = self.file_object.infolist()
        else:
            names = self.file_object.getnames()
            files = self.file_object.getmembers()

        names = '\n'.join(names)
        self.ENCODING, self.DECODING = util.get_encoding(names, encoding)

        dest = os.path.normpath(os.path.abspath(dest))
        if not dest.endswith(os.path.sep): dest += os.path.sep
        if cf == Const.CF_FILENAME: dest += util.get_filename_folder(self.filename)
        elif cf == Const.CF_INTELLIGENT:
            names = names.encode(self.DECODING).decode(self.ENCODING)
            dest += util.get_intelligent_folder(self.filename, names)

        print('Destination : ' + dest)

        for info in files:
            if self._type in (Const.CT_ZIP, Const.CT_RAR):
                try:
                    n = info.filename.encode(self.DECODING).decode(self.ENCODING)
                except:
                    n = info.filename
                    print('Encoding Error : ' + info.filename)
                info.filename = n.replace("\\", os.path.sep).replace("/", os.path.sep)
                self.file_object.extract(info, dest)
                print("extract " + info.filename)
            else:
                if self._type == Const.CT_7Z:
                    n = os.path.join(dest, info.filename)
                    p = os.path.dirname(n)
                    if not os.path.exists(p): os.makedirs(p)
                    outfile = open(n, 'wb')
                    outfile.write(info.read())
                    outfile.close()
                    print("extract " + n)
                else:
                    n = info.name.encode(self.DECODING).decode(self.ENCODING)
                    info.name = n.replace("\\", os.path.sep).replace("/", os.path.sep)
                    info.name = os.path.normpath(info.name)

                    self.file_object.extract(info, dest)
                    print("extract " + info.name)

    def get_list(self, encoding='auto'):
        if not self._is_open: return None
        if self._type == Const.CT_NONE: return None

        if self._type in (Const.CT_ZIP, Const.CT_RAR):
            names = self.file_object.namelist()
            files = self.file_object.infolist()
            names = '\n'.join(names)
            self.ENCODING, self.DECODING = util.get_encoding(names, encoding)
        else:
            names = self.file_object.getnames()
            files = self.file_object.getmembers()

        self.files = []

        for info in files:
            if self._type in (Const.CT_ZIP, Const.CT_RAR):
                try:
                    n = info.filename.encode(self.DECODING).decode(self.ENCODING)
                except:
                    n = info.filename
                    print('Encoding Error : ' + info.filename)
                n = n.replace("\\", os.path.sep).replace("/", os.path.sep)
                s = info.file_size
                d = time.strftime("%Y-%m-%d %H:%M:%S", info.date_time + (0, 0, 0))
            else:
                if self._type == Const.CT_7Z:
                    n = info.filename
                    s = info.size
                    tm = time.localtime(py7zlib.toTimestamp(info.lastwritetime))
                    d = time.strftime("%Y-%m-%d %H:%M:%S", tm)
                else:
                    n = info.name
                    s = info.size
                    tm = time.localtime(info.mtime)
                    d = time.strftime("%Y-%m-%d %H:%M:%S", tm)
            self.files.append(FileInfo(n, s, d))
        return self.files

    def close(self):
        if self._is_open:
            self.filename = ''
            if self._is_open:
                self._is_open = False
                if self._type == Const.CT_7Z:
                    self.fd.close()
                else:
                    self.file_object.close()
            self._type = Const.CT_NONE
            self.files = None
            self.fd = None
            self.file_object = None

    def exit(self):
        if self._is_open: self.close()


class MyArcApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.geo = self.winfo_geometry()
        self.x, self.y = self.winfo_x(), self.winfo_y()
        self.width, self.height = self.winfo_width(), self.winfo_height()
        self.list_height = 0
        self.mx = None
        self.my = None
        self.current_file = ''
        self.current_path = ''

        # fonts for all widgets
        self.option_add("*Font", '"굴림체" 9')

        # font to use for label widgets
        self.option_add("*Label.Font", '"굴림" 11')

        self.option_add("*Button.Font", '"굴림" 8')

        # use gold/black for selections
        self.option_add("*selectBackground", "gold")
        self.option_add("*selectForeground", "black")

        # Top Frame
        self.top_frame = tk.Frame(self)
        self.top_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=False)
        ltxts = ['Extract All', 'Extract Select']
        lcmds = [self.extract_all, self.extract_select]
        for i in range(0, len(ltxts) - 1):
            btn = tk.Button(self.top_frame, text=ltxts[i], command=lcmds[i])
            btn.pack(side=tk.LEFT)

        # Encoding ComboBox
        self.sel_encoding = tk.StringVar()
        val_list = ['auto', 'cp437', 'cp832', 'cp949', 'EUC-KR', 'EUC-JP', 'Shift_JIS', 'UTF-8']
        self.combobox1 = ttk.Combobox(self.top_frame, width=10, textvariable=self.sel_encoding, values=val_list)
        self.combobox1.pack(side=tk.RIGHT)
        self.combobox1.current(0)

        self.sel_extract = tk.StringVar()
        ext_list = ['Current', 'Filename', 'Intelligent', 'No Folder']
        self.combobox2 = ttk.Combobox(self.top_frame, width=15, textvariable=self.sel_extract, values=ext_list)
        self.combobox2.pack(side=tk.RIGHT)
        self.combobox2.current(2)

        # File List & ZipView
        self.paned_win = tk.PanedWindow(borderwidth=0, width=1, orient=tk.HORIZONTAL)
        self.paned_win.pack(fill=tk.BOTH, expand=True)

        self.file_list = tk.Listbox(self.paned_win, width=40, selectmode=tk.EXTENDED)
        self.file_list.bind("<Double-Button-1>", self.on_double_click_file_list)
        self.file_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.paned_win.add(self.file_list)

        self.tree = ttk.Treeview(self.paned_win, columns=('size', 'modified'))
        self.tree.column("size", width=80, stretch=0)
        self.tree.column("modified", width=130, stretch=0)
        self.tree.heading("#0", text="Name")
        self.tree.heading("size", text="size", anchor='w')
        self.tree.heading("modified", text="modified", anchor='w')

        self.paned_win.add(self.tree)

        self.data = []
        self.arc = MyArc()

    def open(self, filename, encoding='auto'):
        self.arc.close()
        self.arc.open(filename)
        fl = self.arc.get_list(encoding=encoding)
        self.tree.delete(*self.tree.get_children())
        for itm in fl:
            self.tree.insert("", 'end', text=itm.name, values=(util.hsize(itm.size), itm.dtm))

    def on_double_click_file_list(self, event):
        widget = event.widget
        selection = widget.curselection()
        if len(selection) > 0:
            idx = selection[0]
            full = self.data[idx][1]
            if os.path.isdir(full):
                self.cd(full)
            else:
                sel = self.sel_encoding.get()
                self.open(full, sel)

    def cd(self, folder):
        print("cd( " + folder + " )")
        dl, fl = util.get_folder_and_file_names(folder)

        self.file_list.delete(0, tk.END)  # clear
        self.data = []

        self.file_list.insert(tk.END, "..")
        self.data.append(("..", os.path.abspath(folder + os.sep + "..")))
        for itm in dl:
            self.file_list.insert(tk.END, itm)
            self.data.append((itm, folder + os.sep + itm))

        for itm in fl:
            self.file_list.insert(tk.END, itm)
            self.data.append((itm, folder + os.sep + itm))

        self.current_path = os.path.abspath(folder)
        if not self.current_path.endswith(os.path.sep):
            self.current_path += os.path.sep

    def extract_all(self):
        if not self.arc.is_open(): return
        ext = self.sel_extract.get()
        sel = self.sel_encoding.get()
        if ext == 'Current':
            self.arc.extract_all(self.current_path,encoding=sel, cf=Const.CF_CURRENT)
        elif ext == 'Filename':
            self.arc.extract_all(self.current_path,encoding=sel, cf=Const.CF_FILENAME)
        elif ext == 'Intelligent':
            self.arc.extract_all(self.current_path,encoding=sel, cf=Const.CF_INTELLIGENT)
        elif ext == 'No Folder':
            self.arc.extract_all(self.current_path,encoding=sel, cf=Const.CF_NOFOLDER)

    def extract_select(self):
        pass

    def exit(self):
        self.arc.close()


def parse(argv):
    command = ''
    option = 'auto'
    filepath = None
    destpath = None
    files = []
    idx = 1
    cnt = len(argv)

    while idx < cnt:
        if argv[idx] in ("h", "-h", "-?", "--help"):
            command = 'help'
            break
        elif argv[idx] in ("l", "-l"):
            command = 'list'
        elif argv[idx] in ("t", "-t"):
            command = 'test'
        elif argv[idx] in ("a", "-a"):
            command = 'create'
        elif argv[idx] in ("af", "-af"):
            command = 'create_each_file'
        elif argv[idx] in ("ai", "-ai"):
            command = 'create_each_folder'
        elif argv[idx] in ("x", "-x"):
            command = 'extract'
        elif argv[idx] in ("xn", "-xn"):
            command = 'extract_no_folder'
        elif argv[idx] in ("xf", "-xf"):
            command = 'extract_filename_folder'
        elif argv[idx] in ("xi", "-xi"):
            command = 'extract_intelligent_folder'
        elif argv[idx] in ("-o", "-enc"):
            idx += 1
            option = argv[idx]
        else:
            fp = os.path.abspath(argv[idx])
            if command.startswith('extract') or command == 'list' or command == 'test':
                if os.path.isfile(fp):
                    files.append(fp)
                else:
                    destpath = fp
            elif command.startswith('create'):
                if os.path.exists(fp):
                    files.append(fp)
                else:
                    filepath = fp
            elif os.path.exists(fp):
                files.append(fp)
            else:
                command = 'help'
        idx += 1
    if Const.DEBUG:
        if command is not None: print('command : ' + command)
        if option is not None: print('option : ' + option)
        if filepath is not None: print('filepath : ' + filepath)
        if destpath is not None: print('destpath : ' + destpath)
        if len(files) > 0: print('files : ' + str(len(files)))
    return command, option, filepath, destpath, files


def use_help():
    print('''
myarc [command] [option] [filename] [destination folder | destination folders and files]
    command : x  = 압축 풀기
              # xd = 하나의 폴더에 다 풀기
              xf = 압축 파일명으로 폴더 생성해서 풀기
              xi = 알아서 폴더 생성해서 풀기
              a  = 하나의 파일로 압축
              af = 파일과 폴더를 각각 압축
              ai = 파일은 하나의 파일로 폴더는 각각 압축
              l  = 목록 보기
    option : o = 인코딩 설정(압축 풀때), 압축 형식 설정(압축 할때)
                cp437, cp832, cp949, EUC-KR, EUC-JP, Shift_JIS, UTF-8, .....
                zip, tar.gz, tar.bz2, 7z
    example : test.zip -- test/test.txt
                          test/test.doc
                          test/test.jpg
                          test/sub/sub.txt
                          test/sub/sub.doc

              -- extract test.zip encoding cp949 intelligent folder --
              /home/user1/down> myzip -xi -o cp949 test.zip

              /home/user1/down/test/test.txt
              /home/user1/down/test/test.doc
              /home/user1/down/test/test.jpg
              /home/user1/down/test/sub/sub.txt
              /home/user1/down/test/sub/sub.doc

              -- extract test.zip auto detect encoding and filename folder --
              /home/user1/down> myzip -xf test.zip

              /home/user1/down/test/test/test.txt
              /home/user1/down/test/test/test.doc
              /home/user1/down/test/test/test.jpg
              /home/user1/down/test/test/sub/sub.txt
              /home/user1/down/test/test/sub/sub.doc
''')
    return


if __name__ == '__main__':
    print('myarc 0.1.0')

    path = os.getcwd()
    argc = len(sys.argv)
    if argc == 1:
        myarc = MyArcApp()
        myarc.title("My Archive Extractor")
        myarc.geometry("600x400")
        myarc.cd(path)
        myarc.mainloop()
        myarc.exit()
    elif argc > 1:
        cmd, opt, file, path, files = parse(sys.argv)
        if path is None: path = os.path.abspath('.')
        if len(cmd) < 1 and len(files) > 0:
            myarc = MyArcApp()
            myarc.title("My Archive Extractor")
            myarc.geometry("600x400")
            myarc.cd(os.path.dirname(os.path.abspath(files[0])))
            myarc.open(files[0])
            myarc.mainloop()
            myarc.exit()
        elif cmd == 'help': use_help()
        elif cmd == 'test':
            myarc = MyArc()
            for f in files:
                myarc.open(f)
                ret = myarc.check()
                if ret is not None: print('Error - ' + ret)
                myarc.close()
            myarc.exit()
        elif cmd == 'list':
            myarc = MyArc()
            for f in files:
                myarc.open(f)
                fl = myarc.get_list(encoding=opt)
                for itm in fl:
                    print("%20s%10s %s" % (itm.dtm, util.hsize(itm.size), itm.name))
                myarc.close()
            myarc.exit()
        elif cmd.startswith('create') and len(files) > 0:
            if opt == 'auto': opt = 'zip'
            myarc = MyArc()
            typ, ext = get_file_type('test.' + opt)
            # print(files)
            cwd = os.getcwd()
            pwd = util.get_diff_folder(files)
            print(cwd)
            print(pwd)
            os.chdir(pwd)
            if cmd == 'create':
                if file is not None:
                    print('Create : ' + file)
                    myarc.open(file, 'w', typ)
                    for f in files:
                        myarc.compress(f.replace(pwd, ''))
                    myarc.close()
            elif cmd == 'create_each_folder':
                for f in files:
                    f = f.replace(pwd, '')
                    myarc.open(cwd + os.path.sep + f + ext, 'w', typ)
                    myarc.compress(f)
                    myarc.close()
            elif cmd == 'create_each_file':
                _file_count = 0
                for f in files:
                    if os.path.isdir(f):
                        f = f.replace(pwd, '')
                        myarc.open(cwd + os.path.sep + f + ext, 'w', typ)
                        myarc.compress(f)
                        myarc.close()
                    elif os.path.isfile(f):
                        _file_count += 1
                if _file_count > 0:
                    myarc.open(cwd + os.path.sep + f + ext, 'w', typ)
                    for f in files:
                        if os.path.isfile(f):
                            myarc.compress(f)
                    myarc.close()

            myarc.exit()
        elif cmd.startswith('extract'):
            myarc = MyArc()
            for f in files:
                myarc.open(f)
                if cmd == 'extract':
                    myarc.extract_all(path, encoding=opt, cf=Const.CF_CURRENT)
                elif cmd == 'extract_no_folder':
                    myarc.extract_all(path, encoding=opt, cf=Const.CF_NOFOLDER)
                elif cmd == 'extract_filename_folder':
                    myarc.extract_all(path, encoding=opt, cf=Const.CF_FILENAME)
                elif cmd == 'extract_intelligent_folder':
                    myarc.extract_all(path, encoding=opt, cf=Const.CF_INTELLIGENT)
                myarc.close()
            myarc.exit()
        else:
            use_help()
